home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / pyshared / cupshelpers / ppds.py < prev   
Text File  |  2009-10-19  |  38KB  |  1,069 lines

  1. #!/usr/bin/env python
  2.  
  3. ## system-config-printer
  4.  
  5. ## Copyright (C) 2006, 2007, 2008, 2009 Red Hat, Inc.
  6. ## Copyright (C) 2006 Florian Festi <ffesti@redhat.com>
  7. ## Copyright (C) 2006, 2007, 2008, 2009 Tim Waugh <twaugh@redhat.com>
  8.  
  9. ## This program is free software; you can redistribute it and/or modify
  10. ## it under the terms of the GNU General Public License as published by
  11. ## the Free Software Foundation; either version 2 of the License, or
  12. ## (at your option) any later version.
  13.  
  14. ## This program is distributed in the hope that it will be useful,
  15. ## but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17. ## GNU General Public License for more details.
  18.  
  19. ## You should have received a copy of the GNU General Public License
  20. ## along with this program; if not, write to the Free Software
  21. ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  22.  
  23. import cups
  24. from .cupshelpers import parseDeviceID
  25. import string
  26. import locale
  27. import os.path
  28. import re
  29. from . import _debugprint, set_debugprint_fn
  30.  
  31. __all__ = ['ppdMakeModelSplit',
  32.            'PPDs']
  33.  
  34. def ppdMakeModelSplit (ppd_make_and_model):
  35.     """
  36.     Split a ppd-make-and-model string into a canonical make and model pair.
  37.  
  38.     @type ppd_make_and_model: string
  39.     @param ppd_make_and_model: IPP ppd-make-and-model attribute
  40.     @return: a string pair representing the make and the model
  41.     """
  42.  
  43.     # If the string starts with a known model name (like "LaserJet") assume
  44.     # that the manufacturer name is missing and add the manufacturer name
  45.     # corresponding to the model name
  46.     ppd_make_and_model.strip ()
  47.     l = ppd_make_and_model.lower ()
  48.     if (l.startswith ("deskjet") or
  49.         l.startswith ("dj ") or l == "dj" or
  50.         (l.startswith ("dj") and len (l) > 2 and l[2].isdigit ()) or
  51.         l.startswith ("laserjet") or
  52.         l.startswith ("lj") or
  53.         l.startswith ("color laserjet") or
  54.         l.startswith ("color lj") or
  55.         l.startswith ("designjet") or
  56.         l.startswith ("officejet") or
  57.         l.startswith ("oj") or
  58.         l.startswith ("photosmart") or
  59.         l.startswith ("ps ") or
  60.         l.startswith ("psc") or
  61.         l.startswith ("edgeline")):
  62.         make = "HP"
  63.         model = ppd_make_and_model
  64.     elif (l.startswith ("stylus") or
  65.           l.startswith ("aculaser")):
  66.         make = "Epson"
  67.         model = ppd_make_and_model
  68.     elif (l.startswith ("stylewriter") or
  69.           l.startswith ("imagewriter") or
  70.           l.startswith ("deskwriter") or
  71.           l.startswith ("laserwriter")):
  72.         make = "Apple"
  73.         model = ppd_make_and_model
  74.     elif (l.startswith ("pixus") or
  75.           l.startswith ("pixma") or
  76.           l.startswith ("selphy") or
  77.           l.startswith ("imagerunner") or
  78.           l.startswith ("bj") or
  79.           l.startswith ("lbp")):
  80.         make = "Canon"
  81.         model = ppd_make_and_model
  82.     elif (l.startswith ("hl") or
  83.           l.startswith ("dcp") or
  84.           l.startswith ("mfc")):
  85.         make = "Brother"
  86.         model = ppd_make_and_model
  87.     elif (l.startswith ("docuprint") or
  88.           l.startswith ("docupage") or
  89.           l.startswith ("phaser") or
  90.           l.startswith ("workcentre") or
  91.           l.startswith ("homecentre")):
  92.         make = "Xerox"
  93.         model = ppd_make_and_model
  94.     elif (l.startswith ("optra") or
  95.           l.startswith ("jetprinter") or
  96.           l.startswith ("color jetprinter")):
  97.         make = "Lexmark"
  98.         model = ppd_make_and_model
  99.     elif (l.startswith ("magicolor") or
  100.           l.startswith ("pageworks") or
  101.           l.startswith ("pagepro")):
  102.         make = "KONICA MINOLTA"
  103.         model = ppd_make_and_model
  104.     elif l.startswith ("aficio"):
  105.         make = "Ricoh"
  106.         model = ppd_make_and_model
  107.     elif l.startswith ("varioprint"):
  108.         make = "Oce"
  109.         model = ppd_make_and_model
  110.     elif (l.startswith ("okipage") or
  111.           l.startswith ("microline")):
  112.         make = "Oki"
  113.         model = ppd_make_and_model
  114.  
  115.     # Handle PPDs provided by Turboprint
  116.     elif l.find ("turboprint") != -1:
  117.         t = ppd_make_and_model.find (" TurboPrint")
  118.         if t != -1:
  119.             t2 = ppd_make_and_model.rfind (" TurboPrint")
  120.             if t != t2:
  121.                 ppd_make_and_model = ppd_make_and_model[t + 12:t2]
  122.             else:
  123.                 ppd_make_and_model = ppd_make_and_model[:t]
  124.         try:
  125.             make, model = ppd_make_and_model.split("_", 1)
  126.         except:
  127.             make = ppd_make_and_model
  128.             model = ''
  129.         make = re.sub (r"(?<=[a-z])(?=[0-9])", " ", make)
  130.         make = re.sub (r"(?<=[a-z])(?=[A-Z])", " ", make)
  131.         model = re.sub (r"(?<=[a-z])(?=[0-9])", " ", model)
  132.         model = re.sub (r"(?<=[a-z])(?=[A-Z])", " ", model)
  133.         model = re.sub (r" Jet", "Jet", model)
  134.         model = re.sub (r"Photo Smart", "PhotoSmart", model)
  135.  
  136.     # Special handling for two-word manufacturers
  137.     elif l.startswith ("konica minolta "):
  138.         make = "KONICA MINOLTA"
  139.         model = ppd_make_and_model[15:]
  140.  
  141.     # Finally, take the first word as the name of the manufacturer.
  142.     else:
  143.         try:
  144.             make, model = ppd_make_and_model.split(" ", 1)
  145.         except:
  146.             make = ppd_make_and_model
  147.             model = ''
  148.  
  149.     # Standardised names for manufacturers.
  150.     makel = make.lower ()
  151.     if (makel.startswith ("konica") and
  152.         makel.endswith ("minolta")):
  153.         make = "KONICA MINOLTA"
  154.         makel = "konica minolta"
  155.     elif (makel.startswith ("hewlett") and
  156.           makel.endswith ("packard")):
  157.         make = "HP"
  158.         makel = "hp"
  159.     elif makel == "lexmark international":
  160.         make = "Lexmark"
  161.         makel = "lexmark"
  162.  
  163.     # HP PPDs give NickNames like:
  164.     # *NickName: "HP LaserJet 4 Plus v2013.111 Postscript (recommended)"
  165.     # Find the version number.
  166.     modell = model.lower ()
  167.     v = modell.find (" v")
  168.     if v != -1 and (model[v + 2].isdigit () or
  169.                     (model[v + 2] == '.' and
  170.                      model[v + 3].isdigit ())):
  171.         # Truncate at that point.
  172.         model = model[:v]
  173.         modell = modell[:v]
  174.  
  175.     for suffix in [" hpijs",
  176.                    " foomatic/",
  177.                    " - ",
  178.                    " w/",
  179.                    " (",
  180.                    " postscript",
  181.                    " ps",
  182.                    " ps1",
  183.                    " ps2",
  184.                    " ps3",
  185.                    " pxl",
  186.                    " series",
  187.                    "_bt",
  188.                    ","]:
  189.         s = modell.find (suffix)
  190.         if s != -1:
  191.             model = model[:s]
  192.             modell = modell[:s]
  193.  
  194.     if makel == "hp":
  195.         modelnames = {"dj": "DeskJet",
  196.                       "lj": "LaserJet",
  197.                       "oj": "OfficeJet",
  198.                       "color lj": "Color LaserJet",
  199.                       "ps ": "PhotoSmart",
  200.                       "hp ": ""}
  201.         for (name, fullname) in modelnames.iteritems ():
  202.             if modell.startswith (name):
  203.                 model = fullname + model[len (name):]
  204.                 modell = model.lower ()
  205.  
  206.     for mfr in [ "Apple", "Canon", "Epson", "Lexmark", "Oki" ]:
  207.         if makel == mfr.lower ():
  208.             make = mfr
  209.  
  210.     model = model.strip ()
  211.     return (make, model)
  212.  
  213. # Some drivers are just generally better than others.
  214. # Here is the preference list:
  215. DRIVER_TYPE_DOWNLOADED_NOW = 5
  216. DRIVER_TYPE_FOOMATIC_RECOMMENDED_NON_POSTSCRIPT = 8
  217. DRIVER_TYPE_VENDOR = 10
  218. DRIVER_TYPE_FOOMATIC_RECOMMENDED_POSTSCRIPT = 15
  219. # Priority of hpcups temporary lowered, as hpcups has many regressions
  220. # compared to hpijs (https://bugs.launchpad.net/hplip, all bugs with
  221. # tag "hpcups")
  222. DRIVER_TYPE_HPCUPS = 18 # (16)
  223. DRIVER_TYPE_FOOMATIC_HPIJS_ON_HP = 17
  224. DRIVER_TYPE_GUTENPRINT_NATIVE_SIMPLIFIED = 20
  225. DRIVER_TYPE_GUTENPRINT_NATIVE = 25
  226. DRIVER_TYPE_SPLIX = 27
  227. DRIVER_TYPE_FOOMATIC_PS = 30
  228. DRIVER_TYPE_FOOMATIC_HPIJS = 40
  229. DRIVER_TYPE_FOOMATIC_GUTENPRINT_SIMPLIFIED = 50
  230. DRIVER_TYPE_FOOMATIC_GUTENPRINT = 60
  231. DRIVER_TYPE_FOOMATIC = 70
  232. DRIVER_TYPE_CUPS = 80
  233. DRIVER_TYPE_FOOMATIC_GENERIC = 90
  234. DRIVER_TYPE_3RD_PARTY_NONFREE = 95
  235. DRIVER_DOES_NOT_WORK = 999
  236. def _getDriverType (ppdname, ppds=None):
  237.     """Decides which of the above types ppdname is."""
  238.     if ppdname.find ("turboprint") != -1:
  239.         return DRIVER_TYPE_3RD_PARTY_NONFREE
  240.     if ppdname.find ("splix")!= -1:
  241.         return DRIVER_TYPE_SPLIX
  242.     if ppdname.find ("hpcups") != -1:
  243.         return DRIVER_TYPE_HPCUPS
  244.     if (ppdname.find (":") == -1 and
  245.         ppdname.find ("/cups-included/") != -1):
  246.         return DRIVER_TYPE_CUPS
  247.     if ppdname.startswith ("foomatic:"):
  248.         # Foomatic (generated) -- but which driver?
  249.         if ppdname.find ("Generic")!= -1:
  250.             return DRIVER_TYPE_FOOMATIC_GENERIC
  251.         if (ppds != None and
  252.             ppds.getInfoFromPPDName (ppdname).\
  253.             get ('ppd-make-and-model', '').find ("(recommended)") != -1):
  254.             if ppds.getInfoFromPPDName (ppdname).\
  255.                get ('ppd-make-and-model', '').find ("Postscript") != -1:
  256.                 return DRIVER_TYPE_FOOMATIC_RECOMMENDED_POSTSCRIPT
  257.             else:
  258.                 return DRIVER_TYPE_FOOMATIC_RECOMMENDED_NON_POSTSCRIPT
  259.         if ppdname.find ("-Postscript")!= -1:
  260.             return DRIVER_TYPE_FOOMATIC_PS
  261.         if ppdname.find ("-hpijs") != -1:
  262.             if ppdname.find ("hpijs-rss") == -1:
  263.                 return DRIVER_TYPE_FOOMATIC_HPIJS
  264.         if ppdname.find ("-gutenprint") != -1:
  265.             if ppdname.find ("-simplified")!= -1:
  266.                 return DRIVER_TYPE_FOOMATIC_GUTENPRINT_SIMPLIFIED
  267.             return DRIVER_TYPE_FOOMATIC_GUTENPRINT
  268.         return DRIVER_TYPE_FOOMATIC
  269.     if ppdname.find ("gutenprint") != -1:
  270.         if (ppdname.find ("/simple") != -1 or
  271.             ppdname.find (".sim-") != -1):
  272.             return DRIVER_TYPE_GUTENPRINT_NATIVE_SIMPLIFIED
  273.         else:
  274.             return DRIVER_TYPE_GUTENPRINT_NATIVE
  275.     if ppdname.find ("-hpijs") != -1:
  276.         if ppdname.find ("hpijs-rss") == -1:
  277.             return DRIVER_TYPE_FOOMATIC_HPIJS
  278.     # Anything else should be a vendor's PPD.
  279.     return DRIVER_TYPE_VENDOR # vendor's own
  280.  
  281.  
  282. class PPDs:
  283.     """
  284.     This class is for handling the list of PPDs returned by CUPS.  It
  285.     indexes by PPD name and device ID, filters by natural language so
  286.     that foreign-language PPDs are not included, and sorts by driver
  287.     type.  If an exactly-matching PPD is not available, it can
  288.     substitute with a PPD for a similar model or for a generic driver.
  289.     """
  290.  
  291.     # Status of match.
  292.     STATUS_SUCCESS = 0
  293.     STATUS_MODEL_MISMATCH = 1
  294.     STATUS_GENERIC_DRIVER = 2
  295.     STATUS_NO_DRIVER = 3
  296.  
  297.     def __init__ (self, ppds, language=None):
  298.         """
  299.         @type ppds: dict
  300.         @param ppds: dict of PPDs as returned by cups.Connection.getPPDs()
  301.  
  302.         @type language: string
  303.     @param language: language name, as given by the first element
  304.         of the pair returned by locale.getlocale()
  305.         """
  306.         self.ppds = ppds.copy ()
  307.         self.makes = None
  308.         self.ids = None
  309.  
  310.         if (language == None or
  311.             language == "C" or
  312.             language == "POSIX"):
  313.             language = "en_US"
  314.  
  315.         u = language.find ("_")
  316.         if u != -1:
  317.             short_language = language[:u]
  318.         else:
  319.             short_language = language
  320.  
  321.         to_remove = []
  322.         for ppdname, ppddict in self.ppds.iteritems ():
  323.             try:
  324.                 natural_language = ppddict['ppd-natural-language']
  325.             except KeyError:
  326.                 continue
  327.  
  328.             if natural_language == "en":
  329.                 # Some manufacturer's PPDs are only available in this
  330.                 # language, so always let them though.
  331.                 continue
  332.  
  333.             if natural_language == language:
  334.                 continue
  335.  
  336.             if natural_language == short_language:
  337.                 continue
  338.  
  339.             to_remove.append (ppdname)
  340.  
  341.         for ppdname in to_remove:
  342.             del self.ppds[ppdname]
  343.  
  344.         # CUPS sets the 'raw' model's ppd-make-and-model to 'Raw Queue'
  345.         # which unfortunately then appears as manufacturer Raw and
  346.         # model Queue.  Use 'Generic' for this model.
  347.         if self.ppds.has_key ('raw'):
  348.             makemodel = self.ppds['raw']['ppd-make-and-model']
  349.             if not makemodel.startswith ("Generic "):
  350.                 self.ppds['raw']['ppd-make-and-model'] = "Generic " + makemodel
  351.  
  352.     def getMakes (self):
  353.         """
  354.     @returns: a list of strings representing makes, sorted according
  355.         to the current locale
  356.     """
  357.         self._init_makes ()
  358.         makes_list = self.makes.keys ()
  359.         makes_list.sort (locale.strcoll)
  360.         try:
  361.             # "Generic" should be listed first.
  362.             makes_list.remove ("Generic")
  363.             makes_list.insert (0, "Generic")
  364.         except ValueError:
  365.             pass
  366.         return makes_list
  367.  
  368.     def getModels (self, make):
  369.         """
  370.     @returns: a list of strings representing models, sorted using
  371.     cups.modelSort()
  372.     """
  373.         self._init_makes ()
  374.         try:
  375.             models_list = self.makes[make].keys ()
  376.         except KeyError:
  377.             return []
  378.         models_list.sort (key=lambda x: x.lower(), cmp=cups.modelSort)
  379.         return models_list
  380.  
  381.     def getInfoFromModel (self, make, model):
  382.         """
  383.     Obtain a list of PPDs that are suitable for use with a
  384.         particular printer model, given its make and model name.
  385.  
  386.     @returns: a dict, indexed by ppd-name, of dicts representing
  387.         PPDs (as given by cups.Connection.getPPDs)
  388.     """
  389.         self._init_makes ()
  390.         try:
  391.             return self.makes[make][model]
  392.         except KeyError:
  393.             return {}
  394.  
  395.     def getInfoFromPPDName (self, ppdname):
  396.         """
  397.     @returns: a dict representing a PPD, as given by
  398.     cups.Connection.getPPDs
  399.     """
  400.         return self.ppds[ppdname]
  401.  
  402.     def orderPPDNamesByPreference (self, ppdnamelist=[],
  403.                                    downloadedfiles=[]):
  404.         """
  405.  
  406.     Sort a list of PPD names by (hard-coded) preferred driver
  407.     type.
  408.  
  409.     @param ppdnamelist: PPD names
  410.     @type ppdnamelist: string list
  411.     @returns: string list
  412.     """
  413.         if len (ppdnamelist) < 1:
  414.             return ppdnamelist
  415.  
  416.         dict = self.getInfoFromPPDName (ppdnamelist[0])
  417.         make_model = dict['ppd-make-and-model']
  418.         mfg, mdl = ppdMakeModelSplit (make_model)
  419.         def getDriverTypeWithBias (x, mfg):
  420.             t = _getDriverType (x, ppds=self)
  421.             for file in downloadedfiles:
  422.                 (path, slash, filename) = file.rpartition ("/")
  423.                 (xpath, xslash, xfilename) = x.rpartition ("/")
  424.                 if filename == xfilename:
  425.                     return DRIVER_TYPE_DOWNLOADED_NOW
  426.             if mfg == "HP" or mfg == "Apollo":
  427.                 if t == DRIVER_TYPE_FOOMATIC_HPIJS:
  428.                     # Prefer HPIJS for HP devices.
  429.                     t = DRIVER_TYPE_FOOMATIC_HPIJS_ON_HP
  430.             return t
  431.  
  432.         def sort_ppdnames (a, b):
  433.             ta = getDriverTypeWithBias (a, mfg)
  434.             tb = getDriverTypeWithBias (b, mfg)
  435.             if ta != tb:
  436.                 if tb < ta:
  437.                     return 1
  438.                 else:
  439.                     return -1
  440.  
  441.             # Prefer C locale localized PPDs to other languages, just
  442.             # because we don't know the user's locale.  This only
  443.             # applies to PPDs provided by gutenprint 5.0; in 5.2 the
  444.             # PPDs are localized properly.
  445.             def is_C_locale (x):
  446.                 try:
  447.                     while x:
  448.                         i = x.find ("C")
  449.                         if i == -1:
  450.                             return False
  451.                         lword = False
  452.                         if i == 0:
  453.                             lword = True
  454.                         elif x[i - 1] not in string.letters:
  455.                             lword = True
  456.  
  457.                         if lword:
  458.                             rword = False
  459.                             if i == (len (x) - 1):
  460.                                 rword = True
  461.                             elif x[i + 1] not in string.letters:
  462.                                 rword = True
  463.                             if rword:
  464.                                 return True
  465.                         
  466.                         x = x[i + 1:]
  467.                 except UnicodeDecodeError:
  468.                     return False
  469.  
  470.             ca = is_C_locale (a)
  471.             cb = is_C_locale (b)
  472.             if ca != cb:
  473.                 # If they compare equal stringwise up to "C", sort.
  474.                 if ca:
  475.                     l = a.find ("C")
  476.                 else:
  477.                     l = b.find ("C")
  478.  
  479.                 if a[:l] == b[:l]:
  480.                     if cb:
  481.                         return 1
  482.                     else:
  483.                         return -1
  484.  
  485.             # String-wise compare.
  486.             if a > b:
  487.                 return 1
  488.             elif a < b:
  489.                 return -1
  490.             return 0
  491.  
  492.         ppdnamelist.sort (sort_ppdnames)
  493.         return ppdnamelist
  494.  
  495.     def getPPDNameFromDeviceID (self, mfg, mdl, description="",
  496.                                 commandsets=[], uri=None,
  497.                                 downloadedfiles=[]):
  498.         """
  499.     Obtain a best-effort PPD match for an IEEE 1284 Device ID.
  500.     The status is one of:
  501.  
  502.       - L{STATUS_SUCCESS}: the match was successful, and an exact
  503.             match was found
  504.  
  505.       - L{STATUS_MODEL_MISMATCH}: a similar match was found, but
  506.             the model name does not exactly match
  507.  
  508.       - L{STATUS_GENERIC_DRIVER}: no match was found, but a
  509.             generic driver is available that can drive this device
  510.             according to its command set list
  511.  
  512.       - L{STATUS_NO_DRIVER}: no match was found at all, and the
  513.             returned PPD name is a last resort
  514.  
  515.     @param mfg: MFG or MANUFACTURER field
  516.     @type mfg: string
  517.     @param mdl: MDL or MODEL field
  518.     @type mdl: string
  519.     @param description: DES or DESCRIPTION field, optional
  520.     @type description: string
  521.     @param commandsets: CMD or COMMANDSET field, optional
  522.     @type commandsets: string
  523.     @param uri: device URI, optional (only needed for debugging)
  524.     @type uri: string
  525.     @returns: an integer,string pair of (status,ppd-name)
  526.     """
  527.         _debugprint ("\n%s %s" % (mfg, mdl))
  528.         self._init_ids ()
  529.  
  530.         # Start with an empty result list and build it up using
  531.         # several search methods, in increasing order of fuzziness.
  532.         ppdnamelist = []
  533.  
  534.         # First, try looking up the device using the manufacturer and
  535.         # model fields from the Device ID exactly as they appear (but
  536.         # case-insensitively).
  537.         mfgl = mfg.lower ()
  538.         mdll = mdl.lower ()
  539.  
  540.         id_matched = False
  541.         try:
  542.             ppdnamelist = self.ids[mfgl][mdll]
  543.             status = self.STATUS_SUCCESS
  544.             id_matched = True
  545.         except KeyError:
  546.             pass
  547.  
  548.         # The HP PPDs say "HP" not "Hewlett-Packard", so try that.
  549.         if mfgl == "hewlett-packard":
  550.             try:
  551.                 ppdnamelist += self.ids["hp"][mdll]
  552.                 status = self.STATUS_SUCCESS
  553.                 id_matched = True
  554.             except KeyError:
  555.                 pass
  556.  
  557.         # Now try looking up the device by ppd-make-and-model.
  558.         _debugprint ("Trying make/model names")
  559.         mdls = None
  560.         self._init_makes ()
  561.         make = None
  562.         if mfgl == "":
  563.             (mfg, mdl) = ppdMakeModelSplit (mdl)
  564.             mfgl = mfg.lower ()
  565.             mdll = mdl.lower ()
  566.  
  567.         mfgrepl = {"hewlett-packard": "hp",
  568.                    "lexmark international": "lexmark"}
  569.         if self.lmakes.has_key (mfgl):
  570.             # Found manufacturer.
  571.             make = self.lmakes[mfgl]
  572.         elif mfgrepl.has_key (mfgl):
  573.             rmfg = mfgrepl[mfgl]
  574.             if self.lmakes.has_key (rmfg):
  575.                 mfg = rmfg
  576.                 mfgl = mfg
  577.                 # Found manufacturer (after mapping to canonical name)
  578.                 make = self.lmakes[mfgl]
  579.  
  580.         if make != None:
  581.             mdls = self.makes[make]
  582.             mdlsl = self.lmodels[make.lower ()]
  583.  
  584.             # Remove manufacturer name from model field
  585.             for prefix in [mfgl, 'hewlett-packard', 'hp']:
  586.                 if mdll.startswith (prefix + ' '):
  587.                     mdl = mdl[len (prefix) + 1:]
  588.                     mdll = mdl.lower ()
  589.  
  590.             if self.lmodels[mfgl].has_key (mdll):
  591.                 model = mdlsl[mdll]
  592.                 ppdnamelist += mdls[model].keys ()
  593.                 status = self.STATUS_SUCCESS
  594.             else:
  595.                 # Make use of the model name clean-up in the
  596.                 # ppdMakeModelSplit () function
  597.                 (mfg2, mdl2) = ppdMakeModelSplit (mfg + " " + mdl)
  598.                 mdl2l = mdl2.lower ()
  599.                 if self.lmodels[mfgl].has_key (mdl2l):
  600.                     model = mdlsl[mdl2l]
  601.                     ppdnamelist += mdls[model].keys ()
  602.                     status = self.STATUS_SUCCESS
  603.       
  604.         if not ppdnamelist and mdls:
  605.             (s, ppds) = self._findBestMatchPPDs (mdls, mdl)
  606.             if s != self.STATUS_NO_DRIVER:
  607.                 status = s
  608.                 ppdnamelist = ppds
  609.  
  610.         if not ppdnamelist and commandsets:
  611.             if type (commandsets) != list:
  612.                 commandsets = commandsets.split (',')
  613.  
  614.             generic = self._getPPDNameFromCommandSet (commandsets)
  615.             if generic:
  616.                 status = self.STATUS_GENERIC_DRIVER
  617.                 ppdnamelist = generic
  618.  
  619.         if not ppdnamelist:
  620.             status = self.STATUS_NO_DRIVER
  621.             fallbacks = ["textonly.ppd", "postscript.ppd"]
  622.             found = False
  623.             for fallback in fallbacks:
  624.                 _debugprint ("'%s' fallback" % fallback)
  625.                 fallbackgz = fallback + ".gz"
  626.                 for ppdpath in self.ppds.keys ():
  627.                     if (ppdpath.endswith (fallback) or
  628.                         ppdpath.endswith (fallbackgz)):
  629.                         ppdnamelist = [ppdpath]
  630.                         found = True
  631.                         break
  632.  
  633.                 if found:
  634.                     break
  635.  
  636.                 _debugprint ("Fallback '%s' not available" % fallback)
  637.  
  638.             if not found:
  639.                 _debugprint ("No fallback available; choosing any")
  640.                 ppdnamelist = [self.ppds.keys ()[0]]
  641.  
  642.         if id_matched:
  643.             _debugprint ("Checking DES field")
  644.             inexact = set()
  645.             if description:
  646.                 for ppdname in ppdnamelist:
  647.                     if ppdname.find ("hpijs"):
  648.                         continue
  649.                     ppddict = self.ppds[ppdname]
  650.                     id = ppddict['ppd-device-id']
  651.                     if not id:
  652.                         continue
  653.                     # Fetch description field.
  654.                     id_dict = parseDeviceID (id)
  655.                     if id_dict["DES"] != description:
  656.                         inexact.add (ppdname)
  657.  
  658.             exact = set (ppdnamelist).difference (inexact)
  659.             _debugprint ("discarding: %s" % inexact)
  660.             if len (exact) >= 1:
  661.                 ppdnamelist = list (exact)
  662.  
  663.         # We've got a set of PPDs, any of which will drive the device.
  664.         # Now we have to choose the "best" one.  This is quite tricky
  665.         # to decide, so let's sort them in order of preference and
  666.         # take the first.
  667.         ppdnamelist = self.orderPPDNamesByPreference (ppdnamelist,
  668.                                                       downloadedfiles)
  669.         _debugprint ("Found PPDs: %s" % str (ppdnamelist))
  670.  
  671.         if not id_matched:
  672.             sanitised_uri = re.sub (pattern="//[^@]*@/?", repl="//",
  673.                                     string=str (uri))
  674.             print "No ID match for device %s:" % sanitised_uri
  675.             print "  <manufacturer>%s</manufacturer>" % mfg
  676.             print "  <model>%s</model>" % mdl
  677.             print "  <description>%s</description>" % description
  678.             try:
  679.                 cmd = reduce (lambda x, y: x + ","+ y, commandsets)
  680.             except TypeError:
  681.                 cmd = ""
  682.  
  683.             print "  <commandset>%s</commandset>" % cmd
  684.  
  685.         print "Using %s (status: %d)" % (ppdnamelist[0], status)
  686.         return (status, ppdnamelist[0])
  687.  
  688.     def _findBestMatchPPDs (self, mdls, mdl):
  689.         """
  690.         Find the best-matching PPDs based on the MDL Device ID.
  691.         This function could be made a lot smarter.
  692.         """
  693.  
  694.         _debugprint ("Trying best match")
  695.         mdll = mdl.lower ()
  696.         if mdll.endswith (" series"):
  697.             # Strip " series" from the end of the MDL field.
  698.             mdll = mdll[:-7]
  699.             mdl = mdl[:-7]
  700.         best_mdl = None
  701.         best_matchlen = 0
  702.         mdlnames = mdls.keys ()
  703.  
  704.         # Perform a case-insensitive model sort on the names.
  705.         mdlnamesl = map (lambda x: (x, x.lower()), mdlnames)
  706.         mdlnamesl.append ((mdl, mdll))
  707.         mdlnamesl.sort (lambda x, y: cups.modelSort(x[1], y[1]))
  708.         i = mdlnamesl.index ((mdl, mdll))
  709.         candidates = [mdlnamesl[i - 1]]
  710.         if i + 1 < len (mdlnamesl):
  711.             candidates.append (mdlnamesl[i + 1])
  712.             _debugprint (candidates[0][0] + " <= " + mdl + " <= " +
  713.                         candidates[1][0])
  714.         else:
  715.             _debugprint (candidates[0][0] + " <= " + mdl)
  716.  
  717.         # Look at the models immediately before and after ours in the
  718.         # sorted list, and pick the one with the longest initial match.
  719.         for (candidate, candidatel) in candidates:
  720.             prefix = os.path.commonprefix ([candidatel, mdll])
  721.             if len (prefix) > best_matchlen:
  722.                 best_mdl = mdls[candidate].keys ()
  723.                 best_matchlen = len (prefix)
  724.                 _debugprint ("%s: match length %d" % (candidate, best_matchlen))
  725.  
  726.         # Did we match more than half of the model name?
  727.         if best_mdl and best_matchlen > (len (mdll) / 2):
  728.             ppdnamelist = best_mdl
  729.             if best_matchlen == len (mdll):
  730.                 status = self.STATUS_SUCCESS
  731.             else:
  732.                 status = self.STATUS_MODEL_MISMATCH
  733.         else:
  734.             status = self.STATUS_NO_DRIVER
  735.             ppdnamelist = None
  736.  
  737.             # Last resort.  Find the "most important" word in the MDL
  738.             # field and look for a match based solely on that.  If
  739.             # there are digits, try lowering the number of
  740.             # significant figures.
  741.             mdlnames.sort (cups.modelSort)
  742.             mdlitems = map (lambda x: (x.lower (), mdls[x]), mdlnames)
  743.             modelid = None
  744.             for word in mdll.split (' '):
  745.                 if modelid == None:
  746.                     modelid = word
  747.  
  748.                 have_digits = False
  749.                 for i in range (len (word)):
  750.                     if word[i].isdigit ():
  751.                         have_digits = True
  752.                         break
  753.  
  754.                 if have_digits:
  755.                     modelid = word
  756.                     break
  757.  
  758.             digits = 0
  759.             digits_start = -1
  760.             digits_end = -1
  761.             for i in range (len (modelid)):
  762.                 if modelid[i].isdigit ():
  763.                     if digits_start == -1:
  764.                         digits_start = i
  765.                     digits_end = i
  766.                     digits += 1
  767.                 elif digits_start != -1:
  768.                     break
  769.             digits_end += 1
  770.             modelnumber = 0
  771.             if digits > 0:
  772.                 modelnumber = int (modelid[digits_start:digits_end])
  773.                 modelpattern = (modelid[:digits_start] + "%d" +
  774.                                 modelid[digits_end:])
  775.                 _debugprint ("Searching for model ID '%s', '%s' %% %d" %
  776.                              (modelid, modelpattern, modelnumber))
  777.                 ignore_digits = 0
  778.                 best_mdl = None
  779.                 found = False
  780.                 while ignore_digits < digits:
  781.                     div = pow (10, ignore_digits)
  782.                     modelid = modelpattern % ((modelnumber / div) * div)
  783.                     _debugprint ("Ignoring %d of %d digits, trying %s" %
  784.                                  (ignore_digits, digits, modelid))
  785.  
  786.                     for (name, ppds) in mdlitems:
  787.                         for word in name.split (' '):
  788.                             if word.lower () == modelid:
  789.                                 found = True
  790.                                 break
  791.  
  792.                         if found:
  793.                             best_mdl = ppds.keys ()
  794.                             break
  795.  
  796.                     if found:
  797.                         break
  798.  
  799.                     ignore_digits += 1
  800.                     if digits < 2:
  801.                         break
  802.  
  803.                 if found:
  804.                     ppdnamelist = best_mdl
  805.                     status = self.STATUS_MODEL_MISMATCH
  806.  
  807.         return (status, ppdnamelist)
  808.  
  809.     def _getPPDNameFromCommandSet (self, commandsets=[]):
  810.         """Return ppd-name list or None, given a list of strings representing
  811.         the command sets supported."""
  812.         try:
  813.             self._init_makes ()
  814.             models = self.makes["Generic"]
  815.         except KeyError:
  816.             return None
  817.  
  818.         def get (*candidates):
  819.             for model in candidates:
  820.                 (s, ppds) = self._findBestMatchPPDs (models, model)
  821.                 if s == self.STATUS_SUCCESS:
  822.                     return ppds
  823.             return None
  824.  
  825.         cmdsets = map (lambda x: x.lower (), commandsets)
  826.         if (("postscript" in cmdsets) or ("postscript2" in cmdsets) or
  827.             ("postscript level 2 emulation" in cmdsets)):
  828.             return get ("PostScript Printer")
  829.         elif (("pclxl" in cmdsets) or ("pcl-xl" in cmdsets) or
  830.               ("pcl6" in cmdsets) or ("pcl 6 emulation" in cmdsets)):
  831.             return get ("PCL 6/PCL XL Printer")
  832.         elif "pcl5e" in cmdsets:
  833.             return get ("PCL 5e Printer")
  834.         elif "pcl5c" in cmdsets:
  835.             return get ("PCL 5c Printer")
  836.         elif ("pcl5" in cmdsets) or ("pcl 5 emulation" in cmdsets):
  837.             return get ("PCL 5 Printer")
  838.         elif "pcl" in cmdsets:
  839.             return get ("PCL 3 Printer")
  840.         elif (("escpl2" in cmdsets) or ("esc/p2" in cmdsets) or
  841.               ("escp2e" in cmdsets)):
  842.             return get ("ESC/P Dot Matrix Printer")
  843.         return None
  844.  
  845.     def _init_makes (self):
  846.         if self.makes:
  847.             return
  848.  
  849.         makes = {}
  850.         lmakes = {}
  851.         lmodels = {}
  852.         for ppdname, ppddict in self.ppds.iteritems ():
  853.             ppd_make_and_model = ppddict['ppd-make-and-model']
  854.             (make, model) = ppdMakeModelSplit (ppd_make_and_model)
  855.             lmake = make.lower ()
  856.             lmodel = model.lower ()
  857.             if not lmakes.has_key (lmake):
  858.                 lmakes[lmake] = make
  859.                 lmodels[lmake] = {}
  860.                 makes[make] = {}
  861.             else:
  862.                 make = lmakes[lmake]
  863.  
  864.             if not lmodels[lmake].has_key (lmodel):
  865.                 lmodels[lmake][lmodel] = model
  866.                 makes[make][model] = {}
  867.             else:
  868.                 model = lmodels[lmake][lmodel]
  869.  
  870.             makes[make][model][ppdname] = ppddict
  871.  
  872.         self.makes = makes
  873.         self.lmakes = lmakes
  874.         self.lmodels = lmodels
  875.  
  876.     def _init_ids (self):
  877.         if self.ids:
  878.             return
  879.  
  880.         ids = {}
  881.         for ppdname, ppddict in self.ppds.iteritems ():
  882.             if not ppddict.has_key ('ppd-device-id'):
  883.                 continue
  884.             id = ppddict['ppd-device-id']
  885.             if not id:
  886.                 continue
  887.  
  888.             # Fix up broken Kyocera IDs
  889.             v = id.find (":Model")
  890.             if v != -1:
  891.                 id = id[:v] + ';' + id[v + 1:]
  892.  
  893.             id_dict = parseDeviceID (id)
  894.             lmfg = id_dict['MFG'].lower ()
  895.             lmdl = id_dict['MDL'].lower ()
  896.  
  897.             # Consider "HP" and "Hewlett-Packard" as equal, as the ID returned
  898.             # by the CUPS "usb" backend and HPLIP's "hp" backend are different
  899.             if lmfg == "hewlett-packard":
  900.                 lmfg = "hp"
  901.  
  902.             bad = False
  903.             if len (lmfg) == 0:
  904.                 _debugprint ("Missing MFG field for %s" % ppdname)
  905.                 bad = True
  906.             if len (lmdl) == 0:
  907.                 _debugprint ("Missing MDL field for %s" % ppdname)
  908.                 bad = True
  909.             if bad:
  910.                 continue
  911.  
  912.             if not ids.has_key (lmfg):
  913.                 ids[lmfg] = {}
  914.  
  915.             if not ids[lmfg].has_key (lmdl):
  916.                 ids[lmfg][lmdl] = []
  917.  
  918.             ids[lmfg][lmdl].append (ppdname)
  919.  
  920.         self.ids = ids
  921.  
  922. def _show_help():
  923.     print "usage: ppds.py [--deviceid] [--list-models] [--list-ids] [--debug]"
  924.  
  925. def _self_test(argv):
  926.     import sys, getopt
  927.     try:
  928.         opts, args = getopt.gnu_getopt (argv[1:], '',
  929.                                         ['help',
  930.                                          'deviceid',
  931.                                          'list-models',
  932.                                          'list-ids',
  933.                                          'debug'])
  934.     except getopt.GetoptError:
  935.         _show_help()
  936.         sys.exit (1)
  937.  
  938.     stdin_deviceid = False
  939.     list_models = False
  940.     list_ids = False
  941.  
  942.     for opt, optarg in opts:
  943.         if opt == "--help":
  944.             _show_help ()
  945.             sys.exit (0)
  946.         if opt == "--deviceid":
  947.             stdin_deviceid = True
  948.         elif opt == "--list-models":
  949.             list_models = True
  950.         elif opt == "--list-ids":
  951.             list_ids = True
  952.         elif opt == "--debug":
  953.             def _dprint(x):
  954.                 try:
  955.                     print x
  956.                 except:
  957.                     pass
  958.  
  959.             set_debugprint_fn (_dprint)
  960.  
  961.     picklefile="pickled-ppds"
  962.     import pickle
  963.     try:
  964.         f = open (picklefile, "r")
  965.         cupsppds = pickle.load (f)
  966.     except IOError:
  967.         f = open (picklefile, "w")
  968.         c = cups.Connection ()
  969.         cupsppds = c.getPPDs ()
  970.         pickle.dump (cupsppds, f)
  971.  
  972.     ppds = PPDs (cupsppds)
  973.     makes = ppds.getMakes ()
  974.     models_count = 0
  975.     for make in makes:
  976.         models = ppds.getModels (make)
  977.         models_count += len (models)
  978.         if list_models:
  979.             print make
  980.             for model in models:
  981.                 print "  " + model
  982.     print "%d makes, %d models" % (len (makes), models_count)
  983.     ppds.getPPDNameFromDeviceID ("HP", "PSC 2200 Series")
  984.     makes = ppds.ids.keys ()
  985.     models_count = 0
  986.     for make in makes:
  987.         models = ppds.ids[make]
  988.         models_count += len (models)
  989.         if list_ids:
  990.             print make
  991.             for model in models:
  992.                 print "  %s (%d)" % (model, len (ppds.ids[make][model]))
  993.                 for driver in ppds.ids[make][model]:
  994.                     print "    " + driver
  995.     print "%d ID makes, %d ID models" % (len (makes), models_count)
  996.  
  997.     print "\nID matching tests\n"
  998.  
  999.     idlist = [
  1000.         # Format is:
  1001.         # (ID string, max status code, expected ppd-make-and-model RE match)
  1002.  
  1003.         # Specific models
  1004.         ("MFG:EPSON;CMD:ESCPL2,BDC,D4,D4PX;MDL:Stylus D78;CLS:PRINTER;"
  1005.          "DES:EPSON Stylus D78;", 1, 'Epson Stylus D68'),
  1006.         ("MFG:Hewlett-Packard;MDL:LaserJet 1200 Series;"
  1007.          "CMD:MLC,PCL,POSTSCRIPT;CLS:PRINTER;", 0, 'HP LaserJet 1200'),
  1008.         ("MFG:Hewlett-Packard;MDL:LaserJet 3390 Series;"
  1009.          "CMD:MLC,PCL,POSTSCRIPT;CLS:PRINTER;", 0, 'HP LaserJet 3390'),
  1010.         ("MFG:Hewlett-Packard;MDL:PSC 2200 Series;CMD:MLC,PCL,PML,DW-PCL,DYN;"
  1011.          "CLS:PRINTER;1284.4DL:4d,4e,1;", 0, "HP PSC 22[01]0"),
  1012.         ("MFG:HEWLETT-PACKARD;MDL:DESKJET 990C;CMD:MLC,PCL,PML;CLS:PRINTER;"
  1013.          "DES:Hewlett-Packard DeskJet 990C;", 0, "HP DeskJet 990C"),
  1014.         ("CLASS:PRINTER;MODEL:HP LaserJet 6MP;MANUFACTURER:Hewlett-Packard;"
  1015.          "DESCRIPTION:Hewlett-Packard LaserJet 6MP Printer;"
  1016.          "COMMAND SET:PJL,MLC,PCLXL,PCL,POSTSCRIPT;", 0, "HP LaserJet 6MP"),
  1017.         # Canon PIXMA iP3000 (from gutenprint)
  1018.         ("MFG:Canon;CMD:BJL,BJRaster3,BSCCe;SOJ:TXT01;MDL:iP3000;CLS:PRINTER;"
  1019.          "DES:Canon iP3000;VER:1.09;STA:10;FSI:03;", 1, "Canon PIXMA iP3000"),
  1020.         ("MFG:HP;MDL:Deskjet 5400 series;CMD:MLC,PCL,PML,DW-PCL,DESKJET,DYN;"
  1021.          "1284.4DL:4d,4e,1;CLS:PRINTER;DES:5440;", 1, "HP DeskJet 5440"),
  1022.         ("MFG:Hewlett-Packard;MDL:HP LaserJet 3390;"
  1023.          "CMD:PJL,MLC,PCL,POSTSCRIPT,PCLXL;",
  1024.          0, "HP LaserJet 3390"),
  1025.  
  1026.         # Generic models
  1027.         ("MFG:New;MDL:Unknown PS Printer;CMD:POSTSCRIPT;",
  1028.          2, "Generic postscript printer"),
  1029.         ("MFG:New;MDL:Unknown PCL6 Printer;CMD:PCLXL;", 2, "Generic PCL 6"),
  1030.         ("MFG:New;MDL:Unknown PCL5e Printer;CMD:PCL5e;", 2, "Generic PCL 5e"),
  1031.         ("MFG:New;MDL:Unknown PCL5c Printer;CMD:PCL5c;", 2, "Generic PCL 5c"),
  1032.         ("MFG:New;MDL:Unknown PCL5 Printer;CMD:PCL5;", 2, "Generic PCL 5"),
  1033.         ("MFG:New;MDL:Unknown PCL3 Printer;CMD:PCL;", 2, "Generic PCL"),
  1034.         ("MFG:New;MDL:Unknown ESC/P Printer;CMD:ESCP2E;", 2, "Generic ESC/P"),
  1035.         ("MFG:New;MDL:Unknown Printer;", 100, None),
  1036.         ]
  1037.  
  1038.     if stdin_deviceid:
  1039.         idlist = [(raw_input ('Device ID: '), 2, '')]
  1040.  
  1041.     all_passed = True
  1042.     for id, max_status_code, modelre in idlist:
  1043.         id_dict = parseDeviceID (id)
  1044.         (status, ppdname) = ppds.getPPDNameFromDeviceID (id_dict["MFG"],
  1045.                                                          id_dict["MDL"],
  1046.                                                          id_dict["DES"],
  1047.                                                          id_dict["CMD"])
  1048.         ppddict = ppds.getInfoFromPPDName (ppdname)
  1049.         if status < max_status_code:
  1050.             success = True
  1051.         else:
  1052.             if status == max_status_code:
  1053.                 match = re.match (modelre, ppddict['ppd-make-and-model'], re.I)
  1054.                 success = match != None
  1055.             else:
  1056.                 success = False
  1057.  
  1058.         if success:
  1059.             result = "PASS"
  1060.         else:
  1061.             result = "*** FAIL ***"
  1062.  
  1063.         print "%s: %s %s (%s)" % (result, id_dict["MFG"], id_dict["MDL"],
  1064.                                   ppddict['ppd-make-and-model'])
  1065.         all_passed = all_passed and success
  1066.  
  1067.     if not all_passed:
  1068.         raise RuntimeError
  1069.